﻿/** \file
* \$Rev: 189 $
* 
* \$Date: 2010-06-28 03:02:44 +0000 (Mon, 28 Jun 2010) $
*
* \$URL: http://chorus-twitter.googlecode.com/svn/trunk/src/utils/SerializeUtils.cs $
*/

using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Drawing;
using System.Globalization;
using System.Xml;
using System.Xml.Serialization;

namespace chorus.utils
{
	/// <summary>
	/// The base class for serializable utility classes needed in Twitter
	/// transactions. Apply a custom TypeConverter type to support Windows
	/// Forms binding.
	/// </summary>
	[TypeConverter(typeof(SerConverter))]
	public abstract class AbstractSerializable : IXmlSerializable
	{
		/// <summary>Needed for the interface, but left as a stub.</summary>
		public System.Xml.Schema.XmlSchema GetSchema() { return null; }
		/// <summary>The string representation of the value.</summary>
		public abstract string StringValue { get; set; }
		/// <summary>Whether the value has been set.</summary>
		public abstract bool IsSet { get; }
		/// <summary>The type of ValueType in children, for TypeConverter support.</summary>
		public abstract Type InnerValueType { get; }
		/// <summary>Return the inner value as an object, for TypeConverter support.</summary>
		public abstract object InnerValue { get; }
		/// <summary>The parse state associated with the serializer that created this instance.</summary>
		public net.AbstractParseState ParseState { get; set; }

		/// <summary>Set val from an XML reader.</summary>
		/// <param name="reader">The XML reader from which a string will be read.</param>
		public void ReadXml(XmlReader reader)
		{
			StatefulXmlReader sreader = reader as StatefulXmlReader;
			if (sreader != null)
				ParseState = sreader.ParseState;
			StringValue = reader.ReadString();
			if (!reader.IsEmptyElement)
				reader.ReadEndElement();
		}

		/// <summary>Write some XML from val.</summary>
		/// <param name="writer">The XML writer to which we'll write a string.</param>
		public void WriteXml(XmlWriter writer)
		{
			StatefulXmlWriter swriter = writer as StatefulXmlWriter;
			if (swriter != null)
				ParseState = swriter.ParseState;
			writer.WriteString(StringValue);
		}

		/// <summary>Convert the serializable object to a string.</summary>
		/// <returns>The string representation of the object.</returns>
		public override string ToString() { return StringValue; }
	}

	/// <summary>
	/// A generic base class that allows for XML serialization.
	/// </summary>
	/// <typeparam name="ValueType">The value type to serialize.</typeparam>
	public abstract class Serializable<ValueType> : AbstractSerializable
	{
		/// <summary>The value.</summary>
		public ValueType val;

		/// <summary>Whether the value has been set.</summary>
		public override bool IsSet { get { return val != null; } }

		/// <summary>The string representation of val.</summary>
		public override string StringValue { get { return val.ToString(); } }

		/// <summary>The type of ValueType in children, for TypeConverter support.</summary>
		public override Type InnerValueType { get { return typeof(ValueType); } }
		/// <summary>Return the inner value as an object, for TypeConverter support.</summary>
		public override object InnerValue { get { return val; } }
	}

	/// <summary>
	/// A serializable integer (or structure) that doesn't fail if the XML is
	/// empty. The value type is nullable.
	/// </summary>
	public class SerStruct<NumType> : Serializable<NumType?>
		where NumType : struct
	{
		/// <summary>The string representation of val.</summary>
		public override string StringValue
		{
			set
			{
				if (value.Length > 0)
					val = (NumType)Convert.ChangeType(value, typeof(NumType));
			}
		}

		/// <summary>Coalesce the value to its default if it is null.</summary>
		public NumType Coalesce { get { return val ?? default(NumType); } }
		/// <summary>The type of ValueType in children, for TypeConverter support.</summary>
		public override Type InnerValueType { get { return typeof(NumType); } }
		/// <summary>Return the inner value as an object, for TypeConverter support.</summary>
		public override object InnerValue { get { return val ?? null; } }
	}

	/// <summary>
	/// The custom type converter for descendents of AbstractSerializable.
	/// </summary>
	public class SerConverter : TypeConverter
	{
		/// <summary>
		/// Says whether this converter supports conversion to a specific type.
		/// Just say that we can do anything, and do the check later when we
		/// have an instance.
		/// </summary>
		/// <param name="context">The type descriptor context.</param>
		/// <param name="destinationType">The type that the object will be converted to.</param>
		/// <returns>Whether this converter supports that type.</returns>
		public override bool CanConvertTo(ITypeDescriptorContext context, Type destinationType) { return true; }

		/// <summary>
		/// "Convert" the given object to its inner value type.
		/// </summary>
		/// <param name="context">The type descriptor context.</param>
		/// <param name="culture">The localization culture.</param>
		/// <param name="value">The value to be converted, hopefully a descendent of AbstractSerializable.</param>
		/// <param name="destinationType">The type to be converted to (hopefully its inner value type).</param>
		/// <returns>The converted object.</returns>
		public override object ConvertTo(ITypeDescriptorContext context, CultureInfo culture, object value, Type destinationType)
		{
			AbstractSerializable abs = (AbstractSerializable)value;
			if (destinationType == abs.InnerValueType)
				return abs.InnerValue;
			return base.ConvertTo(context, culture, value, destinationType);
		}
	}

	/// <summary>
	/// A serializable date of the format that Twitter uses.
	/// </summary>
	public class SerDate : SerStruct<DateTime>
	{
		/// <summary>The string representation of val.</summary>
		public override string StringValue
		{
			set
			{
				if (value != "")
					val = DateTime.ParseExact(value, "ddd MMM dd HH:mm:ss zzz yyyy", null);
			}
		}

		/// <summary>A brief string that shows a relative date/time.</summary>
		public string BriefString
		{
			get
			{
				if (val == null)
					return null;
				DateTime now = DateTime.Now;
				TimeSpan diff = now.Subtract(Coalesce);
				if (diff.TotalMinutes < 1)
					return ((int)diff.TotalSeconds).ToString() + " seconds ago";
				if (diff.TotalHours < 1)
					return ((int)diff.TotalMinutes).ToString() + " minutes ago";
				if (diff.TotalDays < 2)
					return ((int)diff.TotalHours).ToString() + " hours ago";

				if (Coalesce.AddMonths(2) > now)
					return ((int)diff.TotalDays).ToString() + " days ago";
				if (Coalesce.AddYears(2) > now)
					return ((int)(diff.TotalDays * 12 / 365)).ToString() + " months ago";
				return ((int)(diff.TotalDays / 365)).ToString() + " years ago";
			}
		}

		/// <summary>A full description string (time plus relative time).</summary>
		public string FullString
		{
			get { return StringValue + " (" + BriefString + ")"; }
		}

		/// <summary>The default string representation of the date object.</summary>
		/// <returns>The FullString description string.</returns>
		public override string ToString() { return FullString; }
	}

	/// <summary>
	/// An XML-serializable colour.
	/// </summary>
	public class SerColour : SerStruct<Color>
	{
		/// <summary>The string representation of val.</summary>
		public override string StringValue
		{
			get { return System.Convert.ToString(Coalesce.ToArgb(), 16); }
			set
			{
				uint argb = 0xFF000000 | UInt32.Parse(value, NumberStyles.HexNumber);
				val = Color.FromArgb((int)argb);
			}
		}
	}

	/// <summary>
	/// An XML-serializable URI.
	/// </summary>
	public class SerUri : Serializable<Uri>
	{
		/// <summary>The string representation of val.</summary>
		public override string StringValue
		{
			set { val = new Uri(value); }
		}
	}

	/// <summary>
	/// A SerStruct that supports merging into a dictionary by the associated
	/// ParseState's account ID.
	/// </summary>
	/// <typeparam name="NumType"></typeparam>
	public class SerAccountDict<NumType> : SerStruct<NumType>
		where NumType : struct
	{
		/// <summary>
		/// Merge this instance's value into a dictionary by account ID.
		/// </summary>
		/// <param name="dict">The dictionary to merge into.</param>
		public void MergeDict(Dictionary<UInt64, NumType> dict)
		{
			if (ParseState != null && ParseState.Account != null && val != null)
				dict[ParseState.Account.id] = Coalesce;
		}
	}
}
